package no.rmz.genetics;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;



/**
 * Class used to merge two instances of the same
 * class within a particular breeding context.
 */
public class InstanceCombinator {


    private static Map<Class, InstanceCombinator> instanceCombinators =
        new HashMap<>();

    public static  InstanceCombinator getInstanceCombinator(final Class c){
        assert (Breeder.isGeneticClass(c));
        InstanceCombinator result = null;
        if (!instanceCombinators.containsKey(c)) {
            result = new InstanceCombinator(c);
            instanceCombinators.put(c, result);
        } 
        result = instanceCombinators.get(c);
        return result;
    }


    /**
     * The class we are an instance combinator for.
     */
    private Class   c;


    /**
     * Instantiate a new instance combinator for a particular
     * class within a particular breeding context. 
     * 
     * @param cl
     * @param b
     */
    public InstanceCombinator(final Class cl){
        this.c = cl;
    }


    /**
     * If this method is annotated to implicate it as
     * a part of a genetic code.
     * @param m The metod
     * @return True iff the method is genetic.
     */
    public static boolean isGeneticMethod(final Method m){
        final Annotation a = m.getAnnotation(Gene.class);
        // XXX A tad too permissive I think.
        return ( a != null);
    }


    /**
     * True iff the method is a set/get method and
     * it is annotated to be part of a genetic code.
     * @param m The method.
     * @return true iff method is part of a genetic coce.
     */
    public static boolean isGenomeAccessor(final Method m){
        return  (isGeneticMethod(m)) &&
        (isGetMethod(m) || isSetMethod(m));
    }

    /**
     * True iff the method is a "get" method.
     * @param m
     * @return
     */
    public static boolean isGetMethod(final Method m){
        return methodNameHasPrefix(GET_PREFIX, m);
    }

    /**
     * The prefix of the Get methods.
     */
    private final static String GET_PREFIX = "get";

    /**
     * The prefix of the Set methods.
     */
    private final static String SET_PREFIX = "set";

    /**
     * Check if a method�s name starts with  a particular prefix.
     * Note, that the prefix has to be part of a longer name,
     * if the name is -just- the prefix, this test will return
     * false.
     * @param prefix The prefix to look for.
     * @param m The method to check.
     * @return True if the method's name starts with the prefix
     *          AND continues with something else.
     */
    private static boolean methodNameHasPrefix(
            final String prefix,
            final Method m) {
        assert (m != null);
        final String name = m.getName();
        assert (name != null);
        if (name.startsWith(prefix)) {
            return name.length() > prefix.length();
        } else {
            return false;
        }
    }

    public static boolean isSetMethod(final Method m){
        return methodNameHasPrefix(SET_PREFIX, m);
    }

    public static String  getFieldName(final Method m){
        assert ( isGetMethod(m) || isSetMethod(m));
        final String mname = m.getName();

        if ( isSetMethod(m)) {
            return mname.substring(SET_PREFIX.length());
        } else if (isGetMethod(m)) {
            return mname.substring(GET_PREFIX.length());
        } else {
            throw new RuntimeException("Method isn't set or get");
        }
    }



    /**
     * Rewrite to use direct assignment into new
     * object, don�t do this roundabout using FieldValues, which
     * is buggy in any case.
     * @param breeder the breeding context where the combination is taking place.
     * @param g1
     * @param g2
     * @return
     */
    public final Object combineGenetically(
            final Breeder breeder,
            final Object  g1,
            final Object  g2)  throws GeneticException {
        assert (g1.getClass() == c);
        assert (g2.getClass() == c);

        /*
         * Create the result object
         */
        Object result = null;
        try {
            result = c.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GeneticException("Couldn't create instance " + e);
        }
        
        assert(result != null);

        /*
         * Traverse the field values setting the
         * appropriate fields of the result object.
         */

        final Object [] getterArgs = new Object []{};
        final Object [] setterArgs = new Object []{null};



        /**
         * XXX It�s not the field values we�re going for,
         *     it�s the "get" methods.
         */
        for (final FieldValue fv: FieldValue.getFieldValues(c)){
            fv.checkInvariants();

            final Method g = fv.getGetter();


            Object cv = null;

            /**
             * Extract values from both parameters.
             */

            try {
                /**
                 * Produce new value by combining the
                 * two values genetically, using the most
                 * general combinator.
                 */

                // XXX Used to be combineGenetically
                cv =  breeder.breed(
                        g.invoke(g1, getterArgs),
                        g.invoke(g2, getterArgs));
            } catch (InvocationTargetException e) {
                throw new GeneticException("Breeder invokation failed " + e);
            } catch (IllegalAccessException e){
                throw new GeneticException("Illegal access: " + e);
            }

            /*
             * Put the newly computed part into the result object.
             */
            setterArgs[0] = cv;
            try {
                fv.getSetter().invoke(result, setterArgs);
            } catch (InvocationTargetException e) {
                throw new GeneticException("Breeder invokation failed " + e);
            } catch (IllegalAccessException e){
                throw new GeneticException("Illegal access: " + e);
            }
        }


        /*
         * Finally return ;))
         */

        return result;
    }
}
